package com.maozzi.gateway.filter;

import cn.hutool.core.util.ObjectUtil;
import com.google.gson.Gson;
import com.maozzi.common.enums.BizCodeEnum;
import com.maozzi.common.model.ResultVO;
import com.maozzi.common.util.AesUtil;
import com.maozzi.gateway.constant.GatewayConst;
import com.maozzi.gateway.enums.UserTypeEnum;
import com.maozzi.gateway.feign.UserCenterService;
import com.maozzi.gateway.model.UrlInfo;
import com.maozzi.gateway.model.UserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

/**
 * 网关配置，完成认证授权
 *
 * @author maozi
 */
@Slf4j
@Component
public class GatewayFilter implements GlobalFilter, Ordered {

    @Resource
    private UserCenterService userCenterService;
    @Resource
    private ThreadPoolTaskExecutor executor;

    private final static Gson GSON = new Gson();

    @SuppressWarnings("all")
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 认证、鉴权逻辑
        Future<Mono<Void>> future = executor.submit(() -> valid(exchange, chain));

        Mono<Void> mono;
        try {
            mono = future.get();
        } catch (Exception e) {
            log.error("GatewayFilter error, ", e);

            mono = this.monoVoid(exchange, BizCodeEnum.SERVER_ERROR);
        }

        return mono;
    }

    /**
     * 认证、鉴权逻辑
     *
     * @param exchange 请求
     * @param chain    filter 链
     * @return 返回
     */
    private Mono<Void> valid(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String url = request.getURI().getPath();

        // 认证鉴权相关接口自动过滤
        if (GatewayConst.ERROR_URLS.contains(url)
                || GatewayConst.CHECK_URL.equalsIgnoreCase(url)
                || GatewayConst.USER_INFO_URL.equalsIgnoreCase(url)
                || GatewayConst.ACL_USER_INFO_URL.equalsIgnoreCase(url)) {
            return chain.filter(exchange);
        }

        // 查询该 URL 是否需要登录，权限信息
        UrlInfo urlInfo = this.checkUrl(url);
        if (ObjectUtil.isNull(urlInfo) || ObjectUtil.isEmpty(urlInfo.getUrl())) {
            // 无效 url
            return this.monoVoid(exchange, BizCodeEnum.NOT_FOUND, "无效URL");
        }

        // URL 不需要登录可以访问
        if (ObjectUtil.isNull(urlInfo.getLogin()) || !urlInfo.getLogin()) {
            return chain.filter(exchange);
        }

        // 检查是否携带token
        String token = this.getToken(request);
        if (!StringUtils.hasText(token)) {
            // 未登录
            return this.monoVoid(exchange, BizCodeEnum.NEED_LOGIN);
        }

        // 根据token查询用户info
        UserInfo userInfo = this.getUserInfo(token);
        if (ObjectUtil.isEmpty(userInfo)) {
            return this.monoVoid(exchange, BizCodeEnum.NEED_LOGIN, "登录信息异常");
        }

        // 检查当前用户是否有权限
        Boolean b = this.checkUserHasPermission(token, urlInfo);
        if (!b) {
            return this.monoVoid(exchange, BizCodeEnum.FORBIDDEN);
        }

        return chain.filter(exchange);
    }

    /**
     * 查询url信息，是否需要登录、权限
     *
     * @param url url
     * @return 返回
     */
    private UrlInfo checkUrl(String url) {
        String timestamp = String.valueOf(System.currentTimeMillis());
        String sign = AesUtil.encrypt(url, timestamp);

        ResultVO<UrlInfo> vo = userCenterService.checkUrl(timestamp, url, sign);
        return vo.getData();
    }

    /**
     * 获取 header auth token
     *
     * @param request 请求
     * @return 返回
     */
    private String getToken(ServerHttpRequest request) {
        List<String> list = request.getHeaders().get(HttpHeaders.AUTHORIZATION);
        if (Objects.isNull(list) || list.size() != 1) {
            return null;
        }

        return list.get(0).replace("Bearer", "").replaceAll(" ", "");
    }

    /**
     * 返回错误
     *
     * @param exchange webflux
     * @return 返回
     */
    private Mono<Void> monoVoid(ServerWebExchange exchange, BizCodeEnum code) {
        if (ObjectUtil.isNull(code)) {
            code = BizCodeEnum.NEED_LOGIN;
        }

        return monoVoid(exchange, code, code.description());
    }

    /**
     * 返回错误
     *
     * @param exchange webflux
     * @param body     信息
     * @return 返回
     */
    private Mono<Void> monoVoid(ServerWebExchange exchange, BizCodeEnum code, String body) {
        if (ObjectUtil.isNull(code)) {
            code = BizCodeEnum.NEED_LOGIN;
        }
        if (ObjectUtil.isEmpty(body)) {
            body = code.description();
        }

        ResultVO<String> vo = ResultVO.fail(code.getCode(), body);
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        byte[] bytes = GSON.toJson(vo).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = response.bufferFactory().wrap(bytes);
        return response.writeWith(Flux.just(buffer));
    }

    /**
     * 查询用户数据
     *
     * @param token 令牌
     * @return 返回
     */
    private UserInfo getUserInfo(String token) {
        String timestamp = String.valueOf(System.currentTimeMillis());
        String sign = AesUtil.encrypt(token, timestamp);

        ResultVO<UserInfo> vo = userCenterService.getUserInfo(timestamp, token, sign);
        return vo.getData();
    }

    /**
     * 检查用户是否拥有权限
     *
     * @param token 用户token
     * @return false - 无权限
     */
    private Boolean checkUserHasPermission(String token, UrlInfo urlInfo) {
        String timestamp = String.valueOf(System.currentTimeMillis());
        String sign = AesUtil.encrypt(token, timestamp);

        ResultVO<UserInfo> vo = userCenterService.getAclUserInfo(timestamp, token, sign);
        if (ObjectUtil.isEmpty(vo)) {
            log.error("GatewayFilter checkUserHasPermission 检查用户是否拥有权限发生错误，feign 接口返回为空");
            return Boolean.FALSE;
        }

        UserInfo userInfo = vo.getData();
        if (ObjectUtil.isEmpty(vo)) {
            log.error("GatewayFilter checkUserHasPermission 检查用户是否拥有权限发生错误，feign 接口返回data为空");

            return Boolean.FALSE;
        }

        // 检查用户类型
        Integer type = userInfo.getType();
        if (UserTypeEnum.NORMAL.getCode().equals(type)) {
            return Boolean.FALSE;
        }
        if (UserTypeEnum.SUPER_ADMIN.getCode().equals(type)) {
            return Boolean.TRUE;
        }

        // 检查角色
        List<String> roles = urlInfo.getRoles();
        List<String> roleList = userInfo.getRoles();
        if (ObjectUtil.isNotEmpty(roles) && ObjectUtil.isNotEmpty(roleList)) {
            List<String> list = roles.stream().filter(roleList::contains).collect(Collectors.toList());
            return ObjectUtil.isNotEmpty(list);
        }

        // 检查权限码
        List<String> codes = urlInfo.getPermissions();
        List<String> permissions = userInfo.getPermissions();
        if (ObjectUtil.isNotEmpty(codes) && ObjectUtil.isNotEmpty(permissions)) {
            List<String> list = codes.stream().filter(permissions::contains).collect(Collectors.toList());
            return ObjectUtil.isNotEmpty(list);
        }

        return Boolean.FALSE;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
